import { useEffect, useMemo } from 'react' import { Field, Form, Formik, useFormikContext } from 'formik' import { UserAccessGroup, UserAccessPermission } from '@prisma/client' import { trpc, inferQueryOutput, inferMutationInput } from '~/utils/trpc' import IVInputField from '~/components/IVInputField' import IVButton from '~/components/IVButton' import PageHeading from '~/components/PageHeading' import { notify } from '~/components/NotificationCenter' import { useHasPermission } from '~/components/DashboardContext' import { DialogStateReturn } from 'reakit' import IVDialog, { useDialogState } from '~/components/IVDialog' import PermissionSelector from '~/components/PermissionSelector' import SimpleTable from '~/components/SimpleTable' import { dateTimeFormatter } from '~/utils/formatters' import IVAPIError from '~/components/IVAPIError' import UsersList from '~/components/UsersList' import useTable from '~/components/IVTable/useTable' import { useOrgParams } from '~/utils/organization' import IVAlert from '~/components/IVAlert' import IconInfo from '~/icons/compiled/Info' import TeamsSelectorProps from '~/components/TeamsSelector' export default function UsersPage() { useHasPermission('READ_USERS', { redirectToDashboardHome: true }) const canAddUsers = useHasPermission('WRITE_USERS') const { envSlug } = useOrgParams() const addUserDialog = useDialogState() const users = trpc.useQuery(['dashboard.users.index']) const teams = trpc.useQuery(['group.list']) return (
addUserDialog.show(), disabled: !canAddUsers, }, ]} /> {envSlug && (

Users persist across all environments.

)} {users.data && ( <>
)} {canAddUsers && ( )}
) } interface AddUserFormState extends Omit, 'permissions'> { role: UserAccessPermission } function AddUserDialog({ onSubmit, dialog, teams, }: { onSubmit?: () => void dialog: DialogStateReturn teams: Pick[] }) { const addUser = trpc.useMutation('organization.add-user') const isHidden = !dialog.visible && !dialog.animating const { reset: resetMutation } = addUser useEffect(() => { if (isHidden) resetMutation() }, [isHidden, resetMutation]) return ( initialValues={{ email: '', role: 'ACTION_RUNNER', groupIds: [], }} onSubmit={async ({ email, role, groupIds }) => { if (addUser.isLoading) return addUser.mutate( { email, permissions: [role], groupIds, }, { async onSuccess() { dialog.hide() if (onSubmit) onSubmit() notify.success(`An invitation was sent to ${email}.`) }, } ) }} >
{addUser.error?.message !== 'NOT_FOUND' && (
)}
) } function ResetFormToken({ isResetReady }: { isResetReady: boolean }) { const { resetForm } = useFormikContext() useEffect(() => { if (isResetReady) resetForm() }, [resetForm, isResetReady]) return null } function PendingInvitationsTable({ pendingInvitations, }: { pendingInvitations: inferQueryOutput<'dashboard.users.index'>['pendingInvitations'] }) { const { mutate } = trpc.useMutation('organization.revoke-invitation') const { refetchQueries } = trpc.useContext() const rows = useMemo(() => { return pendingInvitations.map((invitation, idx) => { return { key: invitation.id, data: { email: invitation.email, sentAt: dateTimeFormatter.format(invitation.createdAt), action: ( ), }, } }) }, [pendingInvitations, mutate, refetchQueries]) const table = useTable({ data: rows, columns: ['Email', 'Sent at', ''], // sorting tables w/ react components still needs some work; disable sorting until that works isSortable: false, shouldCacheRecords: false, }) if (!rows.length) return null return (

Pending invitations

) }